#include <iostream>
#include <iomanip>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <unistd.h>
#include <sys/time.h>

#include <mpi.h>

typedef struct timeval timeVal;

unsigned long calcRuntime(timeVal start, timeVal end);

int main(int argc, char *argv[])
{
	/* MPI setup */
	char hostname[50];
	int nodes, rank;

	MPI_Init(&argc, &argv);
	MPI_Comm_rank (MPI_COMM_WORLD, &rank);
	MPI_Comm_size (MPI_COMM_WORLD, &nodes);
	gethostname(hostname, 50);
	
	MPI_Barrier(MPI_COMM_WORLD);

	/* parse arguments */
	int c;
	size_t optIterations = 1000;
	bool optDbg = false;

	if (argc >= 1+2)
	{
		size_t argVal;
		while ((c = getopt(argc, argv, "i:?")) != -1)
		{
			switch (c)
			{
				case 'i':
					argVal = atol(optarg);
					optIterations = argVal >= 1 ? argVal : optIterations;
					break;
				case '?':
					optDbg = true; break;
				default:
					break;
			}
		}
	}
	else
	{
		if (rank == 0)
		{
			printf("Syntax Error!\n");
			printf("Usage: %s -i iterations [OPTIONS]\n", argv[0]);
			printf("Options:\n");
			printf("  -?          show debug infos (default: off)\n");
		}
		MPI_Finalize();
		return 0;
	}


	int start = 0, end = 0, workers = 0;
	timeVal t_start,t_end;
	unsigned long runtime;
	double pi=0.0;
	
	if(optDbg){
		printf("# I'm process %2d out of %2d (%s)\n", rank, nodes, hostname);
	}

	if (rank == 0)
	{
		int sendBuf[2];
		double result;
		MPI_Request request;		
		//int start = rank * optIterations / nodes;
		//int end = (rank+1) * optIterations / nodes - 1;
		//if (rank == nodes-1) end = optIterations - 1;
		
		/* distribute work */
		workers = nodes-1;
		gettimeofday(&t_start,NULL);
		for (int i=1; i<=workers; i++)
		{
			start = (i -1) * optIterations / workers;
			end = (i) * optIterations / workers - 1;
			
			if (rank == nodes-1){ 
				end = optIterations - 1;
			}

			if (optDbg)
			{
				printf("#%d: start=%d end=%d \n", i, start, end);
			}

			// MPI-Send ( start, end, dst)
			sendBuf[0] = start;
			sendBuf[1] = end;
			MPI_Isend(sendBuf,2,MPI_INT,i,0,MPI_COMM_WORLD,&request);
		}

		//empfange teilergebnisse
		MPI_Status status;
		pi = 0;

		// Master einsammeln der Teilergebnisse, hochaddieren und mal 4
		for(int i = 1; i <=workers;i++){
			result = 0;
			MPI_Recv(&result,1,MPI_DOUBLE,i,0,MPI_COMM_WORLD,&status);
			pi += result;
		}
		
		//pi = atan * 4;
		pi = 4*pi;

		gettimeofday(&t_end,NULL);
		runtime = calcRuntime(t_start,t_end);				
	} 
	else
	{
		
		
		/* calc pi */
		double atan = 0.0;
		double x = 1.0f;
		double tmp;
		int recvBuff[2];
		MPI_Status status;
		
		pi=0.0;
		// Slaves Recv. start/end und Rechnung starten
		MPI_Recv(recvBuff,2,MPI_INT,0,0,MPI_COMM_WORLD,&status),
		start = recvBuff[0];
		end = recvBuff[1];

		// n:    0 1 2 3 4 5  6  ...
		// x:    1 3 5 7 9 11 13 ...
		// diff: 1 2 3 4 5 6  7  ...
		
		int j = start + (start+1);
		if (optDbg)
		{
			printf("#%d: j=%d \n", rank, j);
		}

		for (int i=start; i<end; i++)
		{
			//tmp = pow(x, j) / j;
			tmp = x / j;
			if ((i & 0x01) != 0x01)
			{
				atan += tmp;
			}
			else
			{
				atan -= tmp;
			}

			j += 2;
		}

		// Slave Send Teilergebnis
		MPI_Send(&atan,1,MPI_DOUBLE,0,0,MPI_COMM_WORLD);
	}

	/* print result */
	if (rank == 0)
	{
		//std::cout << "pi=" << pi << std::endl;
		//std::cout << "# pi =" << pi << std::endl;
		std::cout << "# Worker / Iterations / Runtime (us) / pi" << std::endl;
		std::cout << workers << " " << optIterations << " " << runtime << " " << std::setprecision(9) <<  pi << std::endl;
	}

	//printf("# %2d: done\n", rank);
	MPI_Finalize();
	return 0;
}

/* calcRuntime() */
unsigned long calcRuntime(timeVal start, timeVal end)
{
	/* calc time difference */
	timeVal diff;
	diff.tv_sec = end.tv_sec - start.tv_sec;
	if (start.tv_usec > end.tv_usec){
		diff.tv_usec = (1e6 - end.tv_usec) + start.tv_usec;
	}else{
		diff.tv_usec = end.tv_usec - start.tv_usec;
	}

	return diff.tv_sec * 1e6 + diff.tv_usec;
}

